import numpy as np

# from sklearn.datasets import load_breast_cancer
# from sklearn.model_selection import train_test_split
# from sklearn.metrics import accuracy_score

#from scipy.io import loadmat
import time
start=time.time()
np.random.seed(0)
# data = loadmat('mnistFull.mat')

# X_train = data['X_train'].T
# X_test = X_train[0:5]
# X_test = data['X_test'].T
# X_train = X_test[0:100]
# y_train = data['y_train'][:, 0]
# y_test = y_train[0:5]
# y_train = y_test[0:100]

# # load dataset
# cancer = load_breast_cancer()
# X = cancer.data
# y = 2 * cancer.target - 1
#
# # train/test split
# X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.33, random_state=42)
# np.savez_compressed('data.npz', X_train=X_train, X_test=X_test, y_train=y_train, y_test=y_test)


data = np.load('data.npz')
X_train, X_test, y_train, y_test = data['X_train'], data['X_test'], data['y_train'], data['y_test']


## YOUR SVM implementation

def kernel(_sigma, x1, x2, batch=False):
    """
    Compute pairwise kernel values.
    """
    if batch:
        # Batch input.
        x1_block = np.expand_dims(x1.T, axis=2)
        x1_block = np.repeat(x1_block, len(x2), axis=2)
        x2_block = np.expand_dims(x2.T, axis=1)
        x2_block = np.repeat(x2_block, len(x1), axis=1)
        norms = np.linalg.norm(x1_block - x2_block, axis=0)
        return np.exp(-norms ** 2 / (2 * _sigma))
    else:
        # Single input.
        return np.exp(-np.linalg.norm(x1 - x2.reshape(1, -1)) ** 2 / (2 * _sigma))


def predict(_sigma, _avg_alpha, train_x, test_x):
    kernel_values = kernel(_sigma, train_x, test_x, batch=True)
    pred = np.sign(_avg_alpha.reshape(1, -1) @ kernel_values)
    # y=1 if value >=0, y=-1 if value <0.
    pred[pred == 0] = 1
    return pred.flatten()


# you can code it here or make a new class

T = 1000
sigma = 500
lambda_value = 0.005
n_train, n_test = len(y_train), len(y_test)
beta = np.zeros((T + 1, n_train))
alpha = np.zeros((T + 1, n_train))

# SVM
for t in range(1, T + 1):
    print(t)
    alpha[t - 1] = beta[t - 1] / (lambda_value * t)
    i = np.random.randint(0, n_train)
    masks = np.ones(n_train, dtype=np.bool)
    masks[i] = False
    beta[t, masks] = beta[t - 1, masks]
    if y_train[i] * kernel(sigma, X_train, X_train[i]) < 1:
        beta[t, i] = beta[t - 1, i] + y_train[i]
    else:
        beta[t, i] = beta[t - 1, i]
avg_alpha = np.sum(alpha, axis=0) / T

# Compute predictions.
# y_pred_train = predict(sigma, avg_alpha, X_train, X_train)
y_pred_test = predict(sigma, avg_alpha, X_train, X_test)

# print(f'Training error is {1 - np.mean(y_train == y_pred_train)}')
print(f'Test error is {1 - np.mean(y_test == y_pred_test)}')

end=time.time()
interval=end-start
print(f'{interval}')
